package lpca.utils 
{
	/**
	 * 
	 * @author D.Shakshin
	 */
	import lpca.utils.System;
	public class Wave implements IWave
	{
		protected  static const CELL_START: int = 0; //номер первой волны(начальная точка)
		private var freeCell: Vector.<Point>; // вектор свободных соседних ячеек (неформальный параметр методов locate() executionGenerateWave() 
		protected var start: Point;  // начальная точка		
		private var arrayWave: Array; // массив волн
		private var curLevel: int; //текущая волна
		protected var WavePoints: Vector.< Vector.<Point> >;
		protected var curPoint: Point; //  текущая точка
		protected var maze: IMaze; // лабиринт
		protected var xMax: int; // число столбцов лабиринта
		protected var yMax: int; //  число строк лабиринта
		protected var track: ITrace; // маршрут
		protected var curComplexity: int; // текущая сложность лабиринта
		protected var finish: Point; // конечная точка
		protected var maxlevel: int; // максимальный порядок волны
		protected var _complexity: int; //конечная сложность лабиринта - колличество всех точек волны
			
		private function setWavePoints(vectorPoints: Vector.<Point>): void
		{
			WavePoints.push(vectorPoints.slice());						
			for each(var iterator: Point in vectorPoints)
			{
				var cell: Cell = maze.getCell(iterator);
				cell.valueWave = WavePoints.length;
				maze.setCell(iterator, cell);
			}
			
		}
		public function getWavePoints(levelWave: int): Vector.<Point>
		{
			if (levelWave < WavePoints.length)
				return (WavePoints[levelWave] as Vector.<Point>).slice(0, (WavePoints[levelWave] as Vector.<Point>).length);
			return null;
		}
		
		
		// функции setlevelWave и getLevelWave не являются безопасными(не проверяют на null и не проверяют на границы массива
		protected function setlevelWave(point: Point, value: int): void //установить значение в массив волны  
		{		
			arrayWave[point.y][point.x] = value; 			
		}
		
		public function getlevelWave(point: Point): int // получить значение из массива волны
		{
			if (arrayWave[point.y][point.x] != null) 
				return arrayWave[point.y][point.x]
			else
				return -1;
		}
			
		public function Wave(maze: IMaze, start: Point, complexity: int) 
		{				
			this.maze = maze; 
			this.start = start;
			this.finish = maze.getFinishPoint();
			this._complexity = complexity;
			this.xMax = maze.getSizeX();
			this.yMax = maze.getSizeY();
			this.WavePoints = new Vector.< Vector.<Point> >;
			arrayWave = new Array(yMax+1);
			for (var y: int = 0; y <= yMax+1; y++)
				arrayWave[y] = new Array(xMax + 1);
			initGenerateWave(); 
		}
		
		public function refresh(): void
		{
			initGenerateWave();
		}
		
		protected function initGenerateWave(): void // инициализация генератора волн
		{
			setlevelWave(start, CELL_START); // обозначение начальной точки на массиве волн
			curPoint = start; 
			curComplexity = 0;
			curLevel = 0;
			freeCell = new Vector.<Point>;
			freeCell.push(curPoint); // добавление в вектор первой волны начальной точки
			executeGenerateWave(freeCell.slice(0, freeCell.length), 1); //запуск генератора волн			
			this.maze.setFinishPoint(finish); //обозначаем в лабиринте конечную точку, полученную после генерации волн
			var finishCell: Cell =  this.maze.getCell(finish);
			finishCell.valueWave = this.getCountWave();
			this.maze.setCell(finish, finishCell);
			this.freeCell.splice(0, freeCell.length); //очистка вектора свободных соседних точек {векторе находится последняя волна}
		}
		
		
		protected function executeGenerateWave(vectorWavePoint: Vector.<Point>, level: int): void // генератор волн(curPoint и freeCell - неформальные параметры)
		{		
			this.curComplexity += vectorWavePoint.length; //увеличение сложности алгоритма на колличество точек волны
			if  (( this.curComplexity < this._complexity ) && (vectorWavePoint.length !== 0) ) //если заданная сложность не достигнута и есть точки волны
				{ // вычисляем точки для следующей волны
					this.setWavePoints(this.freeCell);
					this.freeCell.splice(0, this.freeCell.length); //очищаем вектор свободных соседных точек
					do	
						{					
							this.curPoint = vectorWavePoint.pop(); //извлекаем из массива точек предыдущей волны 
							this.locate(this.freeCell, level);		// получаем точки следующей возможной волны: находим свободные соседние ячейки каждой точки волны
						}	
					while ( vectorWavePoint.length > 0); //пока есть точки волны
					level++; // увеличиваем уровень волны
					this.executeGenerateWave(this.freeCell.slice(0, this.freeCell.length + 1), level); // рекурсивно вызываем генератор волн, передавая точки следующей возможной волны
				}
			else //если заданная сложность достигнута или нет свободных точек для следующей волны
				{
					this.maxlevel = level-1; //получаем максимальное число уровней волны
					if (vectorWavePoint.length !== 0) //если ячейки для следующей волны есть
						{
							this.finish = vectorWavePoint[ System.randRange(0, vectorWavePoint.length - 1) ]//генерируем конечную точку из точек следующей волны																					
							this.WavePoints.push(vectorWavePoint);
						}
					else // если точек для следующей волны нету
						{
							this.finish = curPoint; // конечная точка - полседняя точка текущей волны, проверяемая на свободные ячейки
							if (this.maxlevel != 0) // если у лабиринта больше одной точки (сложность не равна нулю)
								this.maxlevel--; // уменьшаем колличество волн(т.к. точек для следующей волны нету)
						}
					this.setlevelWave(this.finish, this.maxlevel); //обозначаем в массиве волн  конечную точку 
					this._complexity = this.curComplexity; //получаем конечную сложность(число точек волны)
				}
		}
		
		protected function locate(freeCell: Vector.<Point>, level: int): void //генерация свободных соседних текущей точки волны curPoint (curPoint - неформальный параметр) и разметка следующей волны
		{			
			for (var dir: int = 0; dir < 4; dir++) //проверка по всем направлениям
				{
					var NearestCell: Cell = this.maze.getNearestCell(curPoint, dir); //получение соседней точки по направлению
					if (NearestCell != null) //если соседняя точка есть
						if (( NearestCell.value == Cell.CELL_EMPTY) && (this.getlevelWave(curPoint.next(dir)) == -1)) // если соседняя точка не "стена" и неотмечана волной						
						{
							var nextPoint: Point = this.curPoint.next(dir); // получаем следующую точку волны
							freeCell.push(nextPoint); //добавляем в массив свободных соседних точек - массив точек следующей волны
							this.setlevelWave((nextPoint), level);	//отмечаем номеров волны полученную соседнею точку					
						}
				}
		}
		
		protected function findTrace():void //вычисление маршрута
		{
			if (finish != null) //если конечная точка есть
			{
				var dir: int = -1 
				var lastValue: int = 0;
				var curValue: int;
				curPoint = finish;
				this.track = new Trace(); //создание вектора точек маршрута
				this.track.push(curPoint); 
				while (this.getlevelWave(curPoint)!= CELL_START) //пока не достигли начальной точки
				{
					lastValue = getlevelWave(curPoint); //получение значение текущей точки
					do //вычисление следующей точки
						{
							dir = (dir + 1) % 4; //изменения направления выбора ячейка
							var nextPoint: Point = this.curPoint.next(dir); //получение соседней ячейки по направлению
							curValue = getlevelWave(nextPoint); //получение значения соседней ячейки
						}
					while ((curValue == -1) || (curValue >= lastValue)); // пока соседней ячейки нету или  пока её значение больше значения текущей точки
					this.curPoint = nextPoint;
					this.track.push(this.curPoint); //добавление следующей точки в стек точек маршрута
				}
			}			
		}
		
		public function  getFinishPoint():Point // получает конечную точку
		{	
			if (finish == null)
				return null;
			else
				return  new Point(finish.x, finish.y);
		}
		
		public function  getTrace(): ITrace //вычисляет и возвращает маршрут
		{
			findTrace();				
			return track;
		}
		
		public function getComplexity(): int //получает сложность маршрута(колличество точек волны)
		{
			if (finish == null)
				return -1;
			else	
				return _complexity;
		}
		
		public function getCountWave(): int //возвращает число волн
		{
			return this.maxlevel+1;
		}
		
		public function show():void //вывод отладочной информации
		{
			var s: String = new String("");
			trace("----Test----");
			for (var y: int = 1; y <= this.yMax; y++)
				{
					for (var x : int = 1; x <= this.xMax; x++)
						if (arrayWave[y][x] != null)
							s += (arrayWave[y][x]%10 as int).toString()
						else
							s += "▓"; 
					trace(s);
					s = "";
				}
			if (track != null)
				this.track.show();
			
		}
		
	}

}